package com.vincent.suchao.util;

import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Message;
import android.util.DisplayMetrics;

/**
 * 
 * Download image asynchronously
 * 
 */
public class ImageAsyncDownloader {
	private final static int CORE_POOL_SIZE = 5;
	private final static int MAX_POOL_SIZE = 50;
	private final static int MAX_KEEP_ALIVE_TIME = 60;

	private static ImageAsyncDownloader mImageAsyncDownloader;

	private HashMap<String, SoftReference<Bitmap>> mBitmapCache;
	private BlockingQueue<Runnable> mBlockingQueue;
	private ExecutorService mThreadPoolExecutor;

	public ImageAsyncDownloader() {
		mBitmapCache = new HashMap<String, SoftReference<Bitmap>>();

		mBlockingQueue = new LinkedBlockingQueue<Runnable>();

		// mThreadPoolExecutor = new ThreadPoolExecutor(CORE_POOL_SIZE,
		// MAX_POOL_SIZE, MAX_KEEP_ALIVE_TIME,
		// TimeUnit.SECONDS, mBlockingQueue);
		mThreadPoolExecutor = Executors.newFixedThreadPool(4);
	}

	// public static ImageAsyncDownloader getInstance() {
	// if (mImageAsyncDownloader == null) {
	// mImageAsyncDownloader = new ImageAsyncDownloader();
	// }
	// return mImageAsyncDownloader;
	// }

	/**
	 * Avoid OOM when decode bitmap scream using @picWidth and @picHeight to
	 * scale original network bitmap.
	 * 
	 * @param context
	 * @param imageUrl
	 * @param imageCallback
	 * @param picWidth
	 * @param picHeight
	 * @return
	 */
	public Bitmap loadBitmap(final Context context, final String imageUrl, final LoadBitmapCallback imageCallback, final int picWidth,
			final int picHeight) {

		if (mBitmapCache.containsKey(imageUrl)) {
			SoftReference<Bitmap> softReference = mBitmapCache.get(imageUrl);
			Bitmap bitmap = softReference.get();
			if (bitmap != null && !bitmap.isRecycled()) {
				return bitmap;
			}
		}
		final Handler handler = new Handler() {
			public void handleMessage(Message message) {
				imageCallback.imageLoaded((Bitmap) message.obj, imageUrl);
			}
		};

		mThreadPoolExecutor.execute(new Runnable() {
			@Override
			public void run() {
				Bitmap bitmap = downloadBitmapFromUrl(context, imageUrl, picWidth, picHeight);
				Message message = handler.obtainMessage(0, bitmap);
				if (bitmap != null) {
					mBitmapCache.put(imageUrl, new SoftReference<Bitmap>(bitmap));
				}
				handler.sendMessage(message);
			}
		});

		return null;
	}

	/**
	 * The image from network will saved in project folder, file name is
	 * imageURL.
	 * 
	 * @return Drawable if the requested image exist in local folder, otherwise
	 *         null
	 */
	public Bitmap loadBitmap(Context context, String imageUrl, final LoadBitmapCallback imageCallback) {
		return loadBitmap(context, imageUrl, imageCallback, -1, -1);
	}

	/**
	 * The image from network will saved in project folder, file name is
	 * imageURL.
	 * 
	 * @return Drawable if the requested image exist in local folder, otherwise
	 *         null
	 */
	public static Bitmap downloadBitmapFromUrl(Context context, String imageUrl, int picWidth, int picHeight) {
		final int DEFAULT_SIZE_W = 480;
		final int DEFAULT_SIZE_H = 800;
		Bitmap bitmap = null;
		if (imageUrl == null) {
			return null;
		}
		InputStream is = null;
		BasicHttpParams httpParams;
		ClientConnectionManager mgr;
		ThreadSafeClientConnManager safeConnManger;
		HttpGet httpRequest;
		HttpClient httpclient;
		HttpResponse response;
		try {
			httpParams = new BasicHttpParams();
			HttpConnectionParams.setConnectionTimeout(httpParams, 8000);
			HttpConnectionParams.setSoTimeout(httpParams, 8000);

			mgr = new DefaultHttpClient().getConnectionManager();
			safeConnManger = new ThreadSafeClientConnManager(httpParams, mgr.getSchemeRegistry());

			httpRequest = new HttpGet(imageUrl);
			httpclient = new DefaultHttpClient(safeConnManger, httpParams);
			response = (HttpResponse) httpclient.execute(httpRequest);
			is = response.getEntity().getContent();
			// The new size we want to scale to
			DisplayMetrics metrics = context.getResources().getDisplayMetrics();

			final int REQUIRED_SIZE_W = metrics.widthPixels < DEFAULT_SIZE_W ? DEFAULT_SIZE_W : metrics.widthPixels;
			final int REQUIRED_SIZE_H = metrics.heightPixels < DEFAULT_SIZE_H ? DEFAULT_SIZE_H : metrics.heightPixels;

			BitmapFactory.Options opt = new BitmapFactory.Options();
			opt.inPreferredConfig = Bitmap.Config.RGB_565;
			// Find the correct scale value. It should be the power of 2.
			int scale = 1;
			if (picWidth != -1 && picHeight != -1) {
				while (((picWidth * picHeight) / scale / 2) >= REQUIRED_SIZE_W * REQUIRED_SIZE_H) {
					scale *= 2;
				}

				opt.inSampleSize = scale;
			} else {
				opt.inJustDecodeBounds = true;
				BitmapFactory.decodeStream(is, null, opt);
				final int minSideLength = Math.min(REQUIRED_SIZE_W, REQUIRED_SIZE_H);
				int tempScale = computeSampleSize(opt, minSideLength, REQUIRED_SIZE_W * REQUIRED_SIZE_H);
				System.out.println(" scale " + tempScale);
				opt.inSampleSize = tempScale;
				opt.inJustDecodeBounds = false;
				is.close();
				httpParams = new BasicHttpParams();
				HttpConnectionParams.setConnectionTimeout(httpParams, 8000);
				HttpConnectionParams.setSoTimeout(httpParams, 8000);
				mgr = new DefaultHttpClient().getConnectionManager();
				safeConnManger = new ThreadSafeClientConnManager(httpParams, mgr.getSchemeRegistry());
				httpRequest = new HttpGet(imageUrl);
				httpclient = new DefaultHttpClient(safeConnManger, httpParams);
				response = (HttpResponse) httpclient.execute(httpRequest);
				is = response.getEntity().getContent();
			}

			if (imageUrl.endsWith("bmp") || imageUrl.endsWith("BMP")) {
				if (is == null) {
					throw new RuntimeException("stream is null");
				} else {
					byte[] data = toByteArray(is);
					if (data != null)
						bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, opt);
				}
			} else {
				bitmap = BitmapFactory.decodeStream(is, null, opt);
			}

			// bufHttpEntity = null;
		} catch (ClientProtocolException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (is != null)
					is.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
			httpRequest = null;
			httpclient = null;
			response = null;
		}

		return bitmap;
	}

	public static byte[] toByteArray(InputStream input) throws IOException {
		ByteArrayOutputStream output = null;
		try {
			output = new ByteArrayOutputStream();
			byte[] buffer = new byte[4096];
			int n = 0;
			while (-1 != (n = input.read(buffer))) {
				output.write(buffer, 0, n);
			}
			output.flush();

			return output.toByteArray();
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (output != null)
				output.close();
		}
		return null;
	}

	public static int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
		int initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels);

		int roundedSize;
		if (initialSize <= 8) {
			roundedSize = 1;
			while (roundedSize < initialSize) {
				roundedSize <<= 1;
			}
		} else {
			roundedSize = (initialSize + 7) / 8 * 8;
		}

		return roundedSize;
	}

	private static int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {
		double w = options.outWidth;
		double h = options.outHeight;
		int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
		int upperBound = (minSideLength == -1) ? 128 : (int) Math.min(Math.floor(w / minSideLength), Math.floor(h / minSideLength));

		if (upperBound < lowerBound) {
			// return the larger one when there is no overlapping zone.
			return lowerBound;
		}

		if ((maxNumOfPixels == -1) && (minSideLength == -1)) {
			return 1;
		} else if (minSideLength == -1) {
			return lowerBound;
		} else {
			return upperBound;
		}
	}

	/***
	 * 
	 * Image download callback
	 * 
	 */
	public interface LoadBitmapCallback {
		public void imageLoaded(Bitmap bitmap, String imageUrl);
	}

	public void shoutdown() {
		if (mThreadPoolExecutor != null) {
			mThreadPoolExecutor.shutdownNow();
			mThreadPoolExecutor = null;
		}
		Set<String> set = mBitmapCache.keySet();
		Iterator<String> it = set.iterator();
		int i = 0;
		while (it.hasNext()) {
			String key = it.next();
			SoftReference<Bitmap> softReference = mBitmapCache.get(key);
			Bitmap bitmap = softReference.get();
			if (bitmap != null) {
				bitmap.recycle();
				bitmap = null;
				i++;
			}

		}
	}

}